<?php
/**
 * User: chaofml
 * Desc: crontab表达式解析工具。
 * Date: 2020年8月19日
 * Time: 17:06 周三
 */

 
// 用法  (new CrontabService('*/2 17 * * *'))->isStart();


namespace chaofml\crontab;

class CrontabService
{
    protected $cron;
    protected $min   = []; //分 Arr
    protected $hour  = []; //时 Arr
    protected $day   = []; //日 Arr
    protected $month = []; //月 Arr
    protected $week  = []; //周  Arr
    static    $cache = []; //缓存对应的表达式。
    /**  
    * 获取客户端的真实IP
    * @return bool
    */
    public function __construct($cron=null){
        if($cron){
            $this->setTime($cron);
        }
    }
    /**
     * 解析表达式
     */
    protected function resolve(){
        $cron = $this->cron;
        if(!empty(self::$cache[$cron])){
            list($this->min,$this->hour,$this->day,$this->month,$this->week) = self::$cache[$cron];
            return $this;
        }
        $arr = \preg_split('/\s+/',$cron);
        if(\count($arr)!=5){
            throw new \Exception('解析错误：长度不为5，当前长度为'.\count($arr));
        }
        $this->min   = $this->strToArr($arr[0],0,59);
        $this->hour  = $this->strToArr($arr[1],0,23);
        $this->day   = $this->strToArr($arr[2],1,31);
        $this->month = $this->strToArr($arr[3],1,12);
        $this->week  = $this->strToArr($arr[4],1,7 );
        if(empty($this->min) || empty($this->hour) || empty($this->day) || empty($this->month) || empty($this->week)){
            throw new \Exception('解析错误');
        }
        //缓存
        self::$cache[$cron] = [$this->min,$this->hour,$this->day,$this->month,$this->week];
        return $this;
    }

    /**
     * 将对应位置的字符，解析成数组。如 1,3 返回 [1,3]
     */
    protected function strToArr($exp,$start,$end){
        if(is_numeric($exp)){
            $val = (int)$exp;
            if($val > $end ){
                return [];
            }
            return [$val];
        }else if($exp == "*"){
            return range($start,$end);
        }else if(\strpos($exp,'*/')!==false){
            $step = \substr($exp,2);
            return \range($start,$end,$step);
        }else if(\strpos($exp,',')!==false){
            $arr = \explode(',',$exp);
            $result = [];
            foreach($arr as $v){
                $result = array_merge($result,$this->strToArr($v,$start,$end));
            }
            return $result;
        }else if(\strpos($exp,'-')!==false){
            $step = 1;
            // 处理这种形式：18-29/3,但是18-29/3/2，会按18-29/3来处理。
            if(\strpos($exp,'/') ){
                list($exp,$step) = \explode('/',$exp);
            }
            $arr = \explode('-',$exp);
            if($arr[0] >$end ){
                return [];
            }
            if($arr[0]+$step>$arr[1]){  //针对  20-21/1这种情况
                return [$arr[0]];
            }
            return range($arr[0],$arr[1]>$end?$end:$arr[1],$step);
        }else{ //比如这种情况 1/*
            return [];
        }
    }

    /**
     * 现在是否开始
     * @Return bool true即符合条件
     */
    public function isStart($cron=null,$time=null){
        if($cron){
            $this->setTime($cron);
        }
        if(!$this->cron){
            throw new \Exception('未进行初始化。');
        }
        if($time){ //主要是解决，任务已经过期了，判断过去的某个时刻是否要执行
            $now =  date('i:H:d:m:N',$time);
        }else{
            $now =  date('i:H:d:m:N');
        }
        $arr = \explode(':',$now);
        $checkArr = [$this->min,$this->hour,$this->day,$this->month,$this->week];
        $skipNum = [60,24,31,12,7];
        // 很奇怪，逆虚好像并不会更快
        // for($i=4;$i>=0;$i--){
        for($i=0;$i<5;$i++){
            $v = (int)$arr[$i];
            if(count($checkArr[$i])==$skipNum[$i]){
                continue;
            }
            if(!\in_array($v,$checkArr[$i])){
                return false;
            }
        }
        return true;
    }

    /**
     * 下次执行时间，待完成。
     */
    public function nextTime(){
        //Todo
    }

    /**
     * 距离开始还要多久？
     */
    public function sleepTime(){
        //Todo
    }
    /**
     * 设置定时任务
     */
    public function setTime($cron){
        $this->cron = $cron;
        $this->resolve();
    }
    /**
     * toString方法
     */
    public function __toString(){
        $arr = [];
        $arr[] = "min:"  .\join(',',$this->min);
        $arr[] = "hour:" .\join(',',$this->hour);
        $arr[] = "day:"  .\join(',',$this->day);
        $arr[] = "month:".\join(',',$this->month);
        $arr[] = "week:" .\join(',',$this->week);
        return \join(';',$arr).".\r\n";
    }
    /**
     * 执行任务
     */
    public function run($callback,$cron=null){
        if($this->isStart($cron)){
            $callback();
        }
    }
}

// $t0 = microtime(true);
// $c = new CrontabService();
// for($i=0;$i<10000;$i++){
//     $tmp = $c->isStart('1,2 */3 1-2,18-29 * *');
// }
// $t1 = microtime(true);
// echo $t1-$t0;


// while(true){
    // $c->run(function(){
    //     echo 'hello wolrd';
    // },'*/2 * * * *');
    // $c->setTime('* 11 * * *');
    // echo $c; 
    // $c->run(function (){
        // echo 'hello wolrd';
    // });
// }

// $tmp = $c->isStart('*/2 * * * *',time()-60);
// var_dump($tmp);
// $tmp = $c->isStart(null,time()-60);
// var_dump($tmp);